/**
 * Project Name:Micro-Services-Config-SpringFramework
 * File Name   :MicroServicesBeanDefinitionParser.java
 * Package Name:com.micro.services.config.springframework.schema
 * Date:2015-9-7上午11:44:12
 * Copyright (c) 2015, http://my.oschina.net/httpssl All Rights Reserved.
 *
*/

package com.micro.services.config.springframework.schema;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.micro.services.common.Constants;
import com.micro.services.common.utils.ReflectUtils;
import com.micro.services.common.utils.StringUtils;
import com.micro.services.config.RegistryConfig;

/**
 * 
 * ClassName:MicroServicesBeanDefinitionParser <br/>
 * Function :解析符合Spring标准的自定义的Schema . <br/>
 * Reason	:TODO ADD REASON. <br/>
 * Date     :2015-9-7 上午11:45:51 <br/>
 * @author  :http://my.oschina.net/httpssl
 * @email   :491835898@QQ.COM
 * @since   :JDK 1.7
 * @see
 */
public class MicroServicesBeanDefinitionParser  implements BeanDefinitionParser {

	private static final Logger logger = LoggerFactory.getLogger(MicroServicesBeanDefinitionParser.class);
	
	private final Class<?> beanClass;
	private final boolean required;
	
	public MicroServicesBeanDefinitionParser(Class<?> beanClass, boolean required) {
        this.beanClass = beanClass;
        this.required = required;
    }

	private static boolean isBlank(String target){
		return org.apache.commons.lang3.StringUtils.isBlank(target);
	}
	
	@Override
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		//
		  return parse(element, parserContext, beanClass, required);
	}
	
	private BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
		//
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(beanClass);
        beanDefinition.setLazyInit(false);
        String id = element.getAttribute("id");
        ///当id属性为空时,就给当前节点生成一个自定义规律的id
        if ((isBlank(id)) && required) {
        	StringBuilder error = new StringBuilder();
        	error.append(element).append("'s Id property is empty set!");
//        	throw new IllegalArgumentException(error.toString());
        	
        	String generatedBeanName = element.getAttribute("name");
          	if (generatedBeanName == null || generatedBeanName.length() == 0) {
          	    if (ProtocolConfig.class.equals(beanClass)) {
          	        generatedBeanName = "dubbo";
          	    }else {
          	        generatedBeanName = element.getAttribute("interface");
          	    }
          	}
          	if (generatedBeanName == null || generatedBeanName.length() == 0) {
          		generatedBeanName = beanClass.getName();
          	}
          	id = generatedBeanName; 
          	int counter = 2;
	        while(parserContext.getRegistry().containsBeanDefinition(id)) {
	            id = generatedBeanName + (counter ++);
	        }
        }
        //到目前为止肯定当前节点都有个id了,不管是生成的还是指定的
        if (id != null && id.length() > 0) {
            if (parserContext.getRegistry().containsBeanDefinition(id))  {
        		throw new IllegalStateException("Duplicate spring bean id " + id);
        	}
            parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
            beanDefinition.getPropertyValues().addPropertyValue("id", id);
        }
        /**
         * 接下来就是各种xxConfig去解析各个节点的配置
         * 有空说一下,各个节点负责什么
         */
        
        if(ProtocolConfig.class.equals(beanClass)){
        	//
        	for (String name : parserContext.getRegistry().getBeanDefinitionNames()) {
        		  BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name);
        		  PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol");
        		  if (property != null) {
        			  Object value = property.getValue();
                      if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) {
                    	  //What is the RuntimeBeanReference means?
                          definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id));
                      }
        		  }else{
        			  logger.debug("{}-not-contains-of-[protocol]-attribute-value",name);
        		  }
        	}
        } else if (ServiceBean.class.equals(beanClass)) {
        	//接口实现类的网站类名 com.abc.InterfaceAImpl
        	String className = element.getAttribute("class");
        	if(!isBlank(className)) {
        		  RootBeanDefinition classDefinition = new RootBeanDefinition();
        		  classDefinition.setBeanClass(ReflectUtils.forName(className));
                  classDefinition.setLazyInit(false);
                  parseProperties(element.getChildNodes(), classDefinition);
                  beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id +Constants.IMPLEMENTS_TAG));
        	}
        } else if(ProviderConfig.class.equals(beanClass)){
        	parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
        } else if (ConsumerConfig.class.equals(beanClass)) {
            parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
        }
        Set<String> props = new HashSet<String>();
        ManagedMap<String, TypedStringValue> parameters = null;
        
        for (Method setter : beanClass.getMethods()) {
        	 String name = setter.getName();
        	 //匹配条件,方法是public,方法名称长度>3,方法的参数只有1个,那不就是setxx方法么
        	 if (Modifier.isPublic(setter.getModifiers()) && name.length() > 3 && name.startsWith("set") 
                     && setter.getParameterTypes().length == 1) {
        		 //当前参数 注入的属性的类型
                 Class<?> type = setter.getParameterTypes()[0];
                 //骆驼命名转为分割命名   helloWorldImpl -> hello-world-impl
                 String property = StringUtils.camelToSplitName(name.substring(3,4).toLowerCase() + name.substring(4), "-");
                 props.add(property);
                 Method getter = null;
                 
                 try {
					getter = beanClass.getMethod("get"+name.substring(3),new Class<?>[0]);
				} catch (NoSuchMethodException e) {
					if(logger.isDebugEnabled()){
						e.printStackTrace();
					}
				} catch (SecurityException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
                
                if (getter == null 
                         || !Modifier.isPublic(getter.getModifiers())
                         || !type.equals(getter.getReturnType())) {
                     continue;
                }
                ///扩展参数..每个对象都可以有扩展参数
                if ("parameters".equals(property)) {
                    parameters = parseParameters(element.getChildNodes(), beanDefinition);
                } 
                ///扩展方法..
                else if ("methods".equals(property)) {
                	//动态注入Methods
                    parseMethods(id, element.getChildNodes(), beanDefinition, parserContext);
                } else if ("arguments".equals(property)) {
                    parseArguments(id, element.getChildNodes(), beanDefinition, parserContext);
                } else {
                	String value = element.getAttribute(property);
                	 if (value != null) {
                		 value = value.trim();
                		 if (value.length() > 0) {
                			 if ("registry".equals(property) && RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(value)) {
                				 RegistryConfig registryConfig = new RegistryConfig();
                				 registryConfig.setAddress(RegistryConfig.NO_AVAILABLE);
                				 beanDefinition.getPropertyValues().addPropertyValue(property, registryConfig);
                			 } else if ("registry".equals(property) && value.indexOf(',') != -1) {
                				 parseMultiRef("registries", value, beanDefinition, parserContext);
                			 } else if("provider".equals(property) && value.indexOf(',') != -1) {
                				 parseMultiRef("providers", value, beanDefinition, parserContext);
                			 } else if("protocol".equals(property) && value.indexOf(',') != -1) {
                				 parseMultiRef("protocols", value, beanDefinition, parserContext);
                			 } else {
                				 Object reference;
                				 //如果是基础类型
                				 if (ReflectUtils.isPrimitive(type)) {
                					 if ("async".equals(property) && "false".equals(value)
                                             || "timeout".equals(property) && "0".equals(value)
                                             || "delay".equals(property) && "0".equals(value)
                                             || "version".equals(property) && "0.0.0".equals(value)
                                             || "stat".equals(property) && "-1".equals(value)
                                             || "reliable".equals(property) && "false".equals(value)) {
                                         // 兼容旧版本xsd中的default值
                                         value = null;
                                     }
                                     reference = value;
                				 } else if ("protocol".equals(property) 
//                                         && ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(value)
                                         && (! parserContext.getRegistry().containsBeanDefinition(value)
                                                 || ! ProtocolConfig.class.getName().equals(parserContext.getRegistry().getBeanDefinition(value).getBeanClassName()))) {
                                     if ("dubbo:provider".equals(element.getTagName())) {
                                         logger.warn("Recommended replace <dubbo:provider protocol=\"" + value + "\" ... /> to <dubbo:protocol name=\"" + value + "\" ... />");
                                     }
                                     // 兼容旧版本配置
                                     ProtocolConfig protocol = new ProtocolConfig();
                                     protocol.setName(value);
                                     reference = protocol;
                                 }
//                				 else if ("monitor".equals(property) 
//                                         && (! parserContext.getRegistry().containsBeanDefinition(value)
//                                                 || ! MonitorConfig.class.getName().equals(parserContext.getRegistry().getBeanDefinition(value).getBeanClassName()))) {
//                                     // 兼容旧版本配置
//                                     reference = convertMonitor(value);
//                                 } 
                                 else if ("onreturn".equals(property)) {
                                     int index = value.lastIndexOf(Constants.DIAN);
                                     String returnRef = value.substring(0, index);
                                     String returnMethod = value.substring(index + 1);
                                     reference = new RuntimeBeanReference(returnRef);
                                     beanDefinition.getPropertyValues().addPropertyValue("onreturnMethod", returnMethod);
                                 } else if ("onthrow".equals(property)) {
                                     int index = value.lastIndexOf(Constants.DIAN);
                                     String throwRef = value.substring(0, index);
                                     String throwMethod = value.substring(index + 1);
                                     reference = new RuntimeBeanReference(throwRef);
                                     beanDefinition.getPropertyValues().addPropertyValue("onthrowMethod", throwMethod);
                                 } else {
                                     if ("ref".equals(property) && parserContext.getRegistry().containsBeanDefinition(value)) {
                                         BeanDefinition refBean = parserContext.getRegistry().getBeanDefinition(value);
                                         if (! refBean.isSingleton()) {
                                             throw new IllegalStateException("The exported service ref " + value + " must be singleton! Please set the " + value + " bean scope to singleton, eg: <bean id=\"" + value+ "\" scope=\"singleton\" ...>");
                                         }
                                     }
                                     reference = new RuntimeBeanReference(value);
                                 }
 		                        beanDefinition.getPropertyValues().addPropertyValue(property, reference);
                			 }
                		 }
                	 }
                }
        	 }
        }
        
        NamedNodeMap attributes = element.getAttributes();
        int len = attributes.getLength();
        for (int i = 0; i < len; i++) {
            Node node = attributes.item(i);
            String name = node.getLocalName();
            if (! props.contains(name)) {
                if (parameters == null) {
                    parameters = new ManagedMap<String, TypedStringValue>();
                }
                String value = node.getNodeValue();
                parameters.put(name, new TypedStringValue(value, String.class));
            }
        }
        if (parameters != null) {
            beanDefinition.getPropertyValues().addPropertyValue("parameters", parameters);
        }
		return beanDefinition;
	}
	
	private static void parseProperties(NodeList nodeList, RootBeanDefinition beanDefinition) {
	        if (nodeList != null && nodeList.getLength() > 0) {
	            for (int i = 0; i < nodeList.getLength(); i++) {
	                Node node = nodeList.item(i);
	                if (node instanceof Element) {
	                    if ("property".equals(node.getNodeName())|| "property".equals(node.getLocalName())) {
	                        String name = ((Element) node).getAttribute("name");
	                        if (name != null && name.length() > 0) {
	                            String value = ((Element) node).getAttribute("value");
	                            String ref = ((Element) node).getAttribute("ref");
	                            if (value != null && value.length() > 0) {
	                                beanDefinition.getPropertyValues().addPropertyValue(name, value);
	                            } else if (ref != null && ref.length() > 0) {
	                                beanDefinition.getPropertyValues().addPropertyValue(name, new RuntimeBeanReference(ref));
	                            } else {
	                                throw new UnsupportedOperationException("Unsupported <property name=\"" + name + "\"> sub tag, Only supported <property name=\"" + name + "\" ref=\"...\" /> or <property name=\"" + name + "\" value=\"...\" />");
	                            }
	                        }
	                    }
	                }
	            }
	        }
	}
	
	private static ManagedMap<String, TypedStringValue> parseParameters(NodeList nodeList, RootBeanDefinition beanDefinition) {
        if (nodeList != null && nodeList.getLength() > 0) {
            ManagedMap<String, TypedStringValue> parameters = null;
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                if (node instanceof Element) {
                	//
                    if ("parameter".equals(node.getNodeName()) || "parameter".equals(node.getLocalName())) {
                        if (parameters == null) {
                            parameters = new ManagedMap<String, TypedStringValue>();
                        }
                        String key = ((Element) node).getAttribute("key");
                        String value = ((Element) node).getAttribute("value");
                        boolean hide = "true".equals(((Element) node).getAttribute("hide"));
                        //这里不知道为什么弄出来一个hide
                        if (hide) {
                            key = Constants.HIDE_KEY_PREFIX + key;
                        }
                        parameters.put(key, new TypedStringValue(value,String.class));
                    }
                }
            }
            return parameters;
        }
        return null;
    }
	
	private static void parseMultiRef(String property, String value, RootBeanDefinition beanDefinition,ParserContext parserContext) {
    	String[] values = value.split("\\s*[,]+\\s*");
		ManagedList<RuntimeBeanReference> list = null;
        for (int i = 0; i < values.length; i++) {
            String v = values[i];
            if (v != null && v.length() > 0) {
            	if (list == null) {
                    list = new ManagedList<RuntimeBeanReference>();
                }
            	list.add(new RuntimeBeanReference(v));
            }
        }
        beanDefinition.getPropertyValues().addPropertyValue(property, list);
    }
	
	private void parseNested(Element element, ParserContext parserContext, Class<?> beanClass, 
			boolean required, String tag, String property, String ref, BeanDefinition beanDefinition) {
		NodeList nodeList = element.getChildNodes();
		if (nodeList != null && nodeList.getLength() > 0) {
			boolean first = true;
            for (int i = 0; i < nodeList.getLength(); i++) {
            	Node node = nodeList.item(i);
                if (node instanceof Element) {
                	 if (tag.equals(node.getNodeName()) || tag.equals(node.getLocalName())) {
                		 if (first) {
                             first = false;
                             String isDefault = element.getAttribute("default");
                             if (isDefault == null || isDefault.length() == 0) {
                                 beanDefinition.getPropertyValues().addPropertyValue("default", "false");
                             }
                         }
                		 BeanDefinition subDefinition = parse((Element) node, parserContext, beanClass, required);
                         if (subDefinition != null && ref != null && ref.length() > 0) {
                             subDefinition.getPropertyValues().addPropertyValue(property, new RuntimeBeanReference(ref));
                         }
                	 }
                }
            }
		}
	}
	
	private void parseMethods(String id, NodeList nodeList, RootBeanDefinition beanDefinition,ParserContext parserContext) {
		if (nodeList != null && nodeList.getLength() > 0) {
			ManagedList<BeanDefinitionHolder> methods = null;
			for (int i = 0; i < nodeList.getLength(); i++) {
				Node node = nodeList.item(i);
                if (node instanceof Element) {
                	Element element = (Element) node;
                	if ("method".equals(node.getNodeName()) || "method".equals(node.getLocalName())) {
                		String methodName = element.getAttribute("name");
                		 if (isBlank(methodName)) {
                             throw new IllegalArgumentException("<micro:method> name attribute == null");
                         }
                		 if (methods == null) {
                             methods = new ManagedList<BeanDefinitionHolder>();
                         }
                		 BeanDefinition methodBeanDefinition = parse(((Element) node),parserContext, MethodConfig.class, false);
                		 StringBuilder nameBuilder = new StringBuilder();
                		 String name = nameBuilder.append(id).append(Constants.DIAN).append(methodName).toString();;
                         BeanDefinitionHolder methodBeanDefinitionHolder = new BeanDefinitionHolder(methodBeanDefinition, name);
                         methods.add(methodBeanDefinitionHolder);
                	}
                }
			}
			if (methods != null) {
	             beanDefinition.getPropertyValues().addPropertyValue("methods", methods);
	        }
		}
	}
	
	
	private void parseArguments(String id, NodeList nodeList, RootBeanDefinition beanDefinition,ParserContext parserContext) {
		 if (nodeList != null && nodeList.getLength() > 0) {
			 ManagedList<BeanDefinitionHolder> arguments = null;
			  for (int i = 0; i < nodeList.getLength(); i++) {
				  Node node = nodeList.item(i);
				  if (node instanceof Element) {
					  Element element = (Element) node;
					  if ("argument".equals(node.getNodeName()) || "argument".equals(node.getLocalName())) {
						  String argumentIndex = element.getAttribute("index");
						  if (arguments == null) {
	                            arguments = new ManagedList<BeanDefinitionHolder>();
	                      }
						  BeanDefinition argumentBeanDefinition = parse(((Element) node),parserContext, ArgumentConfig.class, false);
						  StringBuilder nameBuilder = new StringBuilder();
	                      String name = nameBuilder.append(id).append(Constants.DIAN).append(argumentIndex).toString();;
	                      BeanDefinitionHolder argumentBeanDefinitionHolder = new BeanDefinitionHolder(argumentBeanDefinition, name);
	                      arguments.add(argumentBeanDefinitionHolder);
					  }
				  }
			  }
			  if (arguments != null) {
	                beanDefinition.getPropertyValues().addPropertyValue("arguments", arguments);
	          }
		 }
	}
	
}


class ProtocolConfig {
	String getName(){return null;}
	void setName(String value){}
}
interface ServiceBean {
	String getName();
}
interface ProviderConfig {
	String getName();
}
interface ConsumerConfig {
	String getName();
}
interface ReferenceBean {
	String getName();
}
interface MethodConfig{
	
}
interface ArgumentConfig{
	
}
